/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package unc.pds.auth;

import java.rmi.RemoteException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.ejb.FinderException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.ejb.TransactionManagement;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import unc.pds.data.Consts;
import unc.pds.data.KeyGen;
import unc.pds.data.RootDataBeanRemote;
import unc.pds.data.RootDataBeanRemoteHome;
import unc.pds.exc.NotEnoughDataException;
import unc.pds.exc.PDSSecurityException;
import unc.pds.exc.UserAlreadyExistException;
import unc.pds.model.GroupRemote;
import unc.pds.model.Model;
import unc.pds.model.ModelFactory;
import unc.pds.model.UserRemote;
import unc.pds.util.Arch;
import unc.pds.util.ArchImpl;

/**
 *
 * @author AIN
 */
public class PSDSecurity implements SessionBean, IPDSSecurity {

    private SessionContext context;
    private Long sessionKey;
    private UserRemote curUser = null;

    // <editor-fold defaultstate="collapsed" desc="EJB infrastructure methods. Click the + sign on the left to edit the code.">;
    // TODO Add code to acquire and use other enterprise resources (DataSource, JMS, enterprise bean, Web services)
    // TODO Add business methods or web service operations
    /**
     * @see javax.ejb.SessionBean#setSessionContext(javax.ejb.SessionContext)
     */
    public void setSessionContext(SessionContext aContext) {
        context = aContext;
    }

    /**
     * @see javax.ejb.SessionBean#ejbActivate()
     */
    public void ejbActivate() {
    }

    /**
     * @see javax.ejb.SessionBean#ejbPassivate()
     */
    public void ejbPassivate() {
    }

    /**
     * @see javax.ejb.SessionBean#ejbRemove()
     */
    public void ejbRemove() {
    }

    // </editor-fold>;
    /**
     * See section 7.10.3 of the EJB 2.0 specification
     * See section 7.11.3 of the EJB 2.1 specification
     */
    /*public void ejbCreate() {
        // TODO implement ejbCreate if necessary, acquire resources
        // This method has access to the JNDI context so resource aquisition
        // spanning all methods can be performed here such as home interfaces
        // and data sources.
    }*/
    public void ejbCreate(Long sesskey) {
        sessionKey = sesskey;
    }

    private Connection getConnection() throws NamingException, SQLException {
        DataSource ds = (DataSource) (new InitialContext()).lookup("oracle/SOS");
        return ds.getConnection();
    }

    public Long login(String nick, String password) throws RemoteException {
        try {
            Long object_type_id = getObjectTypeID(Consts.OBJECT_TYPE__USER);
            Long pass_attr_id = getAttributeID(Consts.ATTR__USER_PASSWORD, object_type_id);
            Long nick_attr_id = getAttributeID(Consts.ATTR__USER_NICKNAME, object_type_id);
            Connection conn = getConnection();
            ResultSet ans = null;
            Statement query = null;
            try {
                query = conn.createStatement();
                String sqlquerry = "SELECT * FROM PARAMS WHERE "
                        + Consts.PARAMS__ATTRIBUTE_ID + "=" + pass_attr_id + " AND "
                        + Consts.PARAMS__VALUE + "='" + password + "' AND "
                        + Consts.PARAMS__OBJECT_ID + " IN ("
                        + "SELECT " + Consts.PARAMS__OBJECT_ID + " FROM PARAMS WHERE "
                        + Consts.PARAMS__ATTRIBUTE_ID + "=" + nick_attr_id + " AND "
                        + Consts.PARAMS__VALUE + "='" + nick + "')";
                ans = query.executeQuery(sqlquerry);
                if (ans.next()) {
                    sessionKey = new Long(ans.getLong(Consts.PARAMS__OBJECT_ID));
                    return sessionKey;
                } else {
                    return null;
                }
            } finally {
                ans.close();
                query.close();
                conn.close();
            }
        } catch (Exception e) {
            throw new RemoteException(e.toString());
        }
    }

    public Long register(Arch doc) throws RemoteException, PDSSecurityException {
        if ((doc.getChild(Consts.ATTR__USER_NICKNAME) == null) || (doc.getChild(Consts.ATTR__USER_PASSWORD) == null)) {
            throw new NotEnoughDataException();
        }
        String nickname = doc.getChild(Consts.ATTR__USER_NICKNAME).getValue();
        if (userExist(nickname)) {
            throw new UserAlreadyExistException();

        }
        String type = Consts.OBJECT_TYPE__USER;
        Long object_type_id = getObjectTypeID(Consts.OBJECT_TYPE__USER);
        Long object_id = KeyGen.getInstance().getKey();
        //добавляем запись юзера в таблицу объектов
        try {
            Connection conn = getConnection();
            Statement query = null;
            try {
                String names = Consts.OBJECTS__OBJECT_ID + ","
                        + Consts.OBJECTS__OBJECT_TYPE_ID + ","
                        + Consts.OBJECTS__NAME;
                String values = ") VALUES ("
                        + object_id + "," + object_type_id + ",'" + nickname + "'";
                query = conn.createStatement();
                String endquerry = "INSERT INTO OBJECTS ("
                        + names
                        + values + ")";
                if (Consts.DEBUG_MODE) System.out.println(endquerry);
                if (query.executeUpdate(endquerry) == 0) {
                    throw new RemoteException("cannot create user: insert return 0");

                }
            } finally {
                query.close();
                conn.close();
            }
        } catch (Exception ex) {
            if (Consts.DEBUG_MODE) System.out.println("SecB.register: " + ex.getLocalizedMessage());
            throw new RemoteException("SecB.register: " + ex.getLocalizedMessage());
        }
        //заполняем поля в PARAMS
        doc.addChild(new ArchImpl(Consts.ATTR__USER_NOTINITIAL,Consts.ATTR__USER_NOTINITIAL));
        try {
            for (int i = 0; i < doc.getChilds().length; i++) {
                Arch child = doc.getChilds()[i];
                if ((child.getName().compareTo(Consts.ARCH_F_NAME) != 0)
                        && (child.getName().compareTo(Consts.ARCH_F_PARENT) != 0)
                        && (child.getName().compareTo(Consts.ARCH_F_TYPE) != 0)) {
                    //addParameter(child.getName(), child.getValue());
                    try {
                        Long attr_id = getAttributeID(child.getName(), object_type_id);
                        Connection conn = getConnection();
                        ResultSet ans = null;
                        Statement query = null;
                        try {
                            query = conn.createStatement();
                            ans = query.executeQuery("INSERT INTO PARAMS ("
                                    + Consts.PARAMS__OBJECT_ID + ","
                                    + Consts.PARAMS__ATTRIBUTE_ID + ","
                                    + Consts.PARAMS__VALUE + ") VALUES ("
                                    + object_id + ","
                                    + attr_id + ",'"
                                    + child.getValue() + "')");
                        } finally {
                            ans.close();
                            query.close();
                            conn.close();
                        }
                    } catch (Exception ex) {
                        if (Consts.DEBUG_MODE) System.out.println("SecB.addUserParameter: " + ex.getLocalizedMessage());
                        throw new Exception("SecB.addUserParameter: " + ex.getLocalizedMessage());
                    }
                }
            }
        } catch (Exception ex) {
            if (Consts.DEBUG_MODE) System.out.println("SecB.register: " + ex.getLocalizedMessage());
            throw new RemoteException("SecB.register: " + ex.getLocalizedMessage());
        }        
        sessionKey = object_id;
        return sessionKey;
    }

    private boolean userExist(String nickname) throws RemoteException {
        try {
            Long object_type_id = getObjectTypeID(Consts.OBJECT_TYPE__USER);
            Long nick_attr_id = getAttributeID(Consts.ATTR__USER_NICKNAME, object_type_id);
            Connection conn = getConnection();
            ResultSet ans = null;
            Statement query = null;
            try {
                query = conn.createStatement();
                String sqlquerry = "SELECT * FROM PARAMS WHERE "
                        + Consts.PARAMS__ATTRIBUTE_ID + "=" + nick_attr_id + " AND "
                        + Consts.PARAMS__VALUE + "='" + nickname + "'";
                ans = query.executeQuery(sqlquerry);
                if (ans.next()) {
                    return true;
                } else {
                    return false;
                }
            } finally {
                ans.close();
                query.close();
                conn.close();
            }
        } catch (Exception e) {
            throw new RemoteException(e.toString());
        }
    }

    private Long getObjectTypeID(String name) throws RemoteException {
        {
            try {
                Connection conn = getConnection();
                ResultSet ans = null;
                Statement query = null;
                try {
                    query = conn.createStatement();
                    ans = query.executeQuery("SELECT * FROM OBJECT_TYPES "
                            + "WHERE NAME='" + name + "'");
                    if (ans.next()) {
                        return new Long(ans.getLong(Consts.OBJECT_TYPES__ID));
                    } else {
                        throw new RemoteException("SecB.getObjTypeID: not such object_type " + name);
                    }
                } finally {
                    ans.close();
                    query.close();
                    conn.close();
                }
            } catch (Exception e) {
                throw new RemoteException("SecB.getObjTypeID: " + e.getLocalizedMessage());
            }
        }
    }

    private Long getAttributeID(String name, Long object_type_id) throws FinderException, RemoteException {

        try {
            Connection conn = getConnection();
            ResultSet ans = null;
            Statement query = null;
            try {
                query = conn.createStatement();
                ans = query.executeQuery("SELECT * FROM ATTRIBUTES "
                        + "WHERE OBJECT_TYPE_ID='" + object_type_id + "' AND "
                        + "NAME='" + name + "'");
                if (ans.next()) {
                    return new Long(ans.getLong(Consts.ATTRIBUTES__ATTRIBUTE_ID));
                } else {
                    throw new FinderException("SecB.getAttrID: not attr_id for " + name);
                }
            } finally {
                ans.close();
                query.close();
                conn.close();
            }
        } catch (Exception e) {
            throw new RemoteException("SecB.getAttr_id: " + e.toString());
        }
    }

    public UserRemote getCurrentUser() throws RemoteException {
        if (sessionKey.compareTo(SecurityST.LoginSessionKey) == 0) {
            //логин насяника
            curUser = null;
        } else {
            if (curUser == null) {
                curUser = (UserRemote) ModelFactory.getByKey(sessionKey, sessionKey);
            }
        }
        return curUser;
    }

    public boolean getCrPermission(Long objkey, String entity) throws RemoteException {
        return getPermission(objkey, entity, null, "create");
    }

    public boolean getPermission(Long objkey, String act) throws RemoteException {
        return getPermission(objkey, null, null, act);
    }

    public boolean getPermission(Long objkey, String field, String act) throws RemoteException {
        return (getPermission(objkey, null, field, act));
    }

    public boolean getPermission(Long objkey, String entity, String field, String act) throws RemoteException {
        //проверяем системные роли
        if (Consts.DEBUG_MODE) System.out.println("SecurityB.getPermossion for " + ModelFactory.getByKey(objkey, sessionKey).getType() + " for " + act);
        int sysperm = getSysPermission(ModelFactory.getByKey(objkey, sessionKey).getType(), act);
        if (Consts.DEBUG_MODE) System.out.println("        system perm is " + sysperm);
        if (sysperm == SecurityST.SECURITY_ACCESS) {
            return true;
        }
        if (sysperm == SecurityST.SECURITY_DENIED) {
            return false;
        }
        //проверяем owner роли
        if (checkOwnerPermission(objkey)) {
            return true;
        }
        entity = (entity == null ?  ModelFactory.getByKey(objkey, sessionKey).getType() : entity);
        try {          
            Long userkey = getCurrentUser().getKey();
            GroupRemote[] grlist = getCurrentUser().getMarkerGroups();
            String genuser = "USER_ID=" + userkey;
            for (int i = 0; i < grlist.length; i++) {
                genuser = genuser + " OR USER_ID=" + grlist[i].getKey();
            }
            grlist = getCurrentUser().getGroups();
            for (int i = 0; i < grlist.length; i++) {
                genuser = genuser + " OR USER_ID=" + grlist[i].getKey();
            }

            Connection conn = getConnection();
            ResultSet ans = null;
            Statement query = null;
            try {
                //ищем у себя же в пермишнах
                query = conn.createStatement();
                String querry = "SELECT * FROM SECGRANTS WHERE "
                        + "OBJECT_ID=" + objkey + " AND ("
                        + genuser + ") AND "
                        + ((entity == null) ? "ENTITY_TYPE IS NULL" : "ENTITY_TYPE='" + entity + "'") + " AND "
                        + ((field == null) ? "FIELD IS NULL" : "FIELD='" + field + "'") + " AND "
                        + "ACTION='" + act + "'";
                if (Consts.DEBUG_MODE) System.out.println(querry);
                ans = query.executeQuery(querry);
                if (ans.next()) {
                    return Boolean.parseBoolean(ans.getString("PERMISSION"));
                } else { //ищем у родителей
                    Model m1 = ModelFactory.getByKey(objkey, sessionKey);
                    entity = (entity == null ? m1.getType() : entity);
                    while (m1.getParent() != null) {
                        m1 = m1.getParent();
                        if (Consts.DEBUG_MODE) System.out.println(querry);
                        querry = "SELECT * FROM SECGRANTS WHERE "
                                + "OBJECT_ID=" + m1.getKey() + " AND ("
                                + genuser + ") AND "
                                + ((entity == null) ? "ENTITY_TYPE IS NULL" : "ENTITY_TYPE='" + entity + "'") + " AND "
                                + ((field == null) ? "FIELD IS NULL" : "FIELD='" + field + "'") + " AND "
                                + "ACTION='" + act + "'";
                        ans = query.executeQuery(querry);
                        if (ans.next()) {
                            return Boolean.parseBoolean(ans.getString("PERMISSION"));
                        }
                    }
                }
                return false;
            } finally {
                ans.close();
                query.close();
                conn.close();
            }

        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RemoteException(ex.getLocalizedMessage());
        }
    }
    
    private boolean checkOwnerPermission(Long objkey) throws RemoteException {
        if (Consts.DEBUG_MODE) System.out.println("PDSSec.getPermission.OwnerCheck for " + objkey);
        try {
            RootDataBeanRemoteHome rdbrh = (RootDataBeanRemoteHome) (new InitialContext()).lookup("ejb/RootDataBean");
            //if (Consts.DEBUG_MODE) System.out.println("        try found");
            RootDataBeanRemote rdbr = rdbrh.findByPrimaryKey(objkey);
            //if (Consts.DEBUG_MODE) System.out.println("        load bean");
            if (rdbr.isOwnerSomehow(getCurrentUser().getKey())) {
                if (Consts.DEBUG_MODE) System.out.println("PDSSec.isOwner: current user is owner for " + objkey);
                return true;
            } else {
                return false;
            }
        } catch (Exception ex) {
            if (Consts.DEBUG_MODE) System.out.println("ERROR: SB.getPermission.isOwner: " + ex.getLocalizedMessage());
            throw new RemoteException("ERROR: SB.getPermission.isOwner: " + ex.getLocalizedMessage());
        }
    }

    //системные пермишны, основанные на уровнях доступа в системе
    private int getSysPermission(String event, String act) throws RemoteException {
        return getSysPermissionFor(getCurrentSysRole(), event, act);
    }

    private int getSysPermissionFor(String role, String event, String act) throws RemoteException {
        if (Consts.DEBUG_MODE) System.out.println("SecurityB.getSysPermFor role is: " + role + " req act: " + act + " for event: " + event);
        try {
            Connection conn = getConnection();
            Statement query = null;
            ResultSet ans = null;
            try {
                query = conn.createStatement();
                String querry = "SELECT * FROM SYSPERM WHERE "
                        + "ROLE='" + role + "' AND "
                        + "OBJTYPE='" + event + "' AND "
                        + "UACTION='" + act + "'";
                ans = query.executeQuery(querry);
                if (ans.next()) {
                    String UACCESS = ans.getString("UACCESS");
                    if (UACCESS.compareTo(SecurityST.SECURITY_ACCESS_ST) == 0) {
                        if (Consts.DEBUG_MODE) System.out.println("        result is: " + SecurityST.SECURITY_ACCESS);
                        return SecurityST.SECURITY_ACCESS;
                    }
                    if (UACCESS.compareTo(SecurityST.SECURITY_DENIED_ST) == 0) {
                        if (Consts.DEBUG_MODE) System.out.println("        result is: " + SecurityST.SECURITY_DENIED);
                        return SecurityST.SECURITY_DENIED;
                    }
                } else {
                    if (Consts.DEBUG_MODE) System.out.println("        result is: " + SecurityST.SECURITY_DEFAULT);
                    return SecurityST.SECURITY_DEFAULT;
                }
                if (Consts.DEBUG_MODE) System.out.println("        result is: " + SecurityST.SECURITY_DEFAULT);
                return SecurityST.SECURITY_DEFAULT;
            } finally {
                ans.close();
                query.close();
                conn.close();
            }
        } catch (Exception ex) {
            throw new RemoteException("SB.getPermission  " + ex.getLocalizedMessage());
        }
    }

    private String getCurrentSysRole() throws RemoteException {
        if (Consts.DEBUG_MODE) System.out.println("PDSSec.getCurSysRole start " + sessionKey);
        if (isAnonimous()) {
            if (Consts.DEBUG_MODE) System.out.println("        result is: " + SecurityST.SECURITY_ANONIMOUS_ROLE);
            return SecurityST.SECURITY_ANONIMOUS_ROLE;
        }
        if (getCurrentUser() != null) {
            try {
                String role = "";
                Connection conn = getConnection();
                Statement query = null;
                ResultSet ans = null;
                try {
                    query = conn.createStatement();
                    String querry = "SELECT * FROM PRIVILEGED WHERE USERID=" + getCurrentUser().getKey();
                    ans = query.executeQuery(querry);
                    if (ans.next()) {
                        role = ans.getString("PRLEVEL");
                    } else {
                        role = SecurityST.SECURITY_DEFAULT_ROLE;
                    }
                    if (Consts.DEBUG_MODE) System.out.println("        result is: " + role);
                    return role;
                } finally {
                    ans.close();
                    query.close();
                    conn.close();
                }
            } catch (Exception ex) {
                throw new RemoteException("SB.getPermission  " + ex.getLocalizedMessage());
            }
        }
        if (Consts.DEBUG_MODE) System.out.println("        result is: " + SecurityST.SECURITY_ANONIMOUS_ROLE);
        return SecurityST.SECURITY_ANONIMOUS_ROLE;
    }

    public boolean isAnonimous() {
        if (sessionKey == null) {
            return true;
        } else {
            return false;
        }
    }

    public boolean setPermission(Long objkey, Long userkey, String entity, String field, String act, boolean access) throws RemoteException {
        try {
            entity = (entity == null ?  ModelFactory.getByKey(objkey, sessionKey).getType() : entity);
            //параметр есть и отличен, значит обновить параметр
            Connection conn = getConnection();
            Statement query = null;
            try {
                query = conn.createStatement();
                String querry = null;
                if (query.executeQuery("SELECT * FROM SECGRANTS WHERE "
                        + "OBJECT_ID=" + objkey + " AND "
                        + "USER_ID=" + userkey + " AND "
                        + ((entity == null) ? "ENTITY_TYPE IS NULL" : "ENTITY_TYPE='" + entity + "'") + " AND "
                        + ((field == null) ? "FIELD IS NULL" : "FIELD='" + field + "'") + " AND "
                        + "ACTION='" + act + "'").next()) {
                    querry = "UPDATE SECGRANTS SET "
                            + "PERMISSION='" + access + "'"
                            + " WHERE "
                            + "OBJECT_ID=" + objkey + " AND "
                            + "USER_ID=" + userkey + " AND "
                            + ((entity == null) ? "ENTITY_TYPE IS NULL" : "ENTITY_TYPE='" + entity + "'") + " AND "
                            + ((field == null) ? "FIELD IS NULL" : "FIELD='" + field + "'") + " AND "
                            + "ACTION='" + act + "'";
                } else {
                    querry = "INSERT INTO SECGRANTS ("
                            + "OBJECT_ID,"
                            + "USER_ID,"
                            + ((entity == null)?"":"ENTITY_TYPE,")
                            + ((field == null) ?"":"FIELD,")
                            + "ACTION,PERMISSION"
                            + ") VALUES ("
                            + objkey + ","
                            + userkey + ","
                            + ((entity == null)?"":"'" + entity + "',")
                            + ((field == null) ?"":"'" + field + "'," )
                            + "'" + act + "','" + access + "')";
                }
                if (Consts.DEBUG_MODE) System.out.println(querry);
                return query.executeUpdate(querry) > 0;
            } finally {
                query.close();
                conn.close();
            }

        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RemoteException(ex.getLocalizedMessage());
        }
    }

    public boolean dropAllPermission(Long user, Long dropfor) throws RemoteException {
        try {
            Connection conn = getConnection();
            ResultSet ans = null;
            Statement query = null;
            try {
                query = conn.createStatement();
                ans = query.executeQuery("DELETE FROM SECGRANTS"
                        + " WHERE "
                        + "OBJECT_ID=" + user + " AND "
                        + "USER_ID=" + dropfor);
                return true;
            } finally {
                if (ans != null) {
                    ans.close();
                }
                if (query != null) {
                    query.close();
                }
                conn.close();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RemoteException(ex.getLocalizedMessage());
        }
    }

    public boolean dropPermission(Long entityKey, Long dropfor, String entity, String field, String act) throws RemoteException {
        try {
            Connection conn = getConnection();
            ResultSet ans = null;
            Statement query = null;
            try {
                query = conn.createStatement();
                ans = query.executeQuery("DELETE FROM SECGRANTS"
                        + " WHERE "
                        + "OBJECT_ID=" + entityKey + " AND "
                        + "USER_ID=" + dropfor + " AND "
                        + "ENTITY='" + entity + "' AND "
                        + "FIELD='" + field + "' AND "
                        + "ACTION='" + act + "'");
                return true;
            } finally {
                if (ans != null) {
                    ans.close();
                }
                if (query != null) {
                    query.close();
                }
                conn.close();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RemoteException(ex.getLocalizedMessage());
        }
    }

    public boolean dropPermission(Long entityKey, String entity, String field, String act) throws RemoteException {
        try {
            Connection conn = getConnection();
            ResultSet ans = null;
            Statement query = null;
            try {
                query = conn.createStatement();
                ans = query.executeQuery("DELETE FROM SECGRANTS"
                        + " WHERE "
                        + "OBJECT_ID=" + entityKey + " AND "
                        + "ENTITY='" + entity + "' AND "
                        + "FIELD='" + field + "' AND "
                        + "ACTION='" + act + "'");
                return true;
            } finally {
                if (ans != null) {
                    ans.close();
                }
                if (query != null) {
                    query.close();
                }
                conn.close();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RemoteException(ex.getLocalizedMessage());
        }
    }

    /*public void setProtectOn(Long entityKey) throws RemoteException {
        throw new UnsupportedOperationException("Not supported yet.");
    }*/
}
